package bancosys.tec.i18n;

import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import bancosys.tec.exception.LocalizedMessageHolder;

/**
 * Classe auxiliar para centralizar a busca por mensagens em arquivos de bundle.
 * 
 * @author jrenaut
 * @since 06/06/08
 */
public final class MessageResolver {

    private final Locale locale;

    /**
     * C'tor
     * 
     * @param locale Locale
     */
    private MessageResolver(final Locale locale) {
        this.locale = locale;
    }

    /**
     * Cria um resolver para o Locale especificado.
     * 
     * @param locale Locale
     * @return MessageResolver
     */
    public static MessageResolver forLocale(final Locale locale) {
        if (locale == null) {
            throw new NullPointerException("Locale must not be null!");
        }
        return new MessageResolver(locale);
    }

    /**
     * Cria um resolver para o Locale especificado.
     * 
     * @return MessageResolver
     */
    public static MessageResolver forDefaultLocale() {
        return MessageResolver.forLocale(Locale.getDefault());
    }

    /**
     * Procura no bundle dado a mensagem com a chave dada aplicando os argumentos. Se a mensagem não for encontrada retorna a chave.
     * 
     * @param bundleBaseName String
     * @param key String
     * @param arguments Object[]
     * @return String
     */
    public String resolveMessage(final String bundleBaseName, final String key, final Object... arguments) {
        if (key != null && bundleBaseName != null) {
            try {
                // throws MRE if null
                return this.doResolveMessage(bundleBaseName, key, arguments);
            } catch (MissingResourceException e) {
                try {
                    return this.doResolveMessage("jmine-tec-component-messages", "component.chaveOuBundleNaoEncontrado", key,
                            bundleBaseName, Arrays.toString(arguments));
                } catch (MissingResourceException e2) {
                    return String.format("Dados de mensagem não encontrados: %s no bundle %s %s.", key, bundleBaseName, arguments);
                }
            }
        } else {
            return this.resolveMessage("jmine-tec-component-messages", "component.chaveOuBundleNaoDefinido");
        }
    }

    /**
     * Effectively resolves the message
     * 
     * @param bundleBaseName bundleBaseName
     * @param key key
     * @param arguments arguments
     * @return String
     */
    private String doResolveMessage(final String bundleBaseName, final String key, final Object... arguments) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if (classLoader == null) {
            classLoader = MessageResolver.class.getClassLoader();
        }
        ResourceBundle resource = ResourceBundle.getBundle(bundleBaseName, this.locale, classLoader);
        // throws MRE if null
        String format = resource.getString(key);
        return arguments.length > 0 ? MessageFormat.format(format, this.resolveArguments(arguments)) : format;
    }

    /**
     * Resolves all arguments
     * 
     * @param arguments arguments
     * @return resolved arguments
     */
    private Object[] resolveArguments(Object[] arguments) {
        Object[] resolved = new Object[arguments.length];
        for (int i = 0; i < arguments.length; i++) {
            resolved[i] = this.resolve(arguments[i]);
        }
        return resolved;
    }

    /**
     * Resolves the string that represents an object. If this object supports localized expression, uses it.
     * 
     * @param object object
     * @return string
     */
    private String resolve(Object object) {
        if (object instanceof LocalizedMessageHolder) {
            return ((LocalizedMessageHolder) object).getMessage(this.locale);
        }
        return String.valueOf(object);
    }
}
